---
title: "Spring Data Jpa Transaction"
date: 2020-6-22
categories:
- java
- spring-jpa
tags:
---

<div id="content">
<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#org8363207">Transaction Annotation</a></li>
<li><a href="#orgf5ce027">Entity Tracking</a>
<ul>
<li><a href="#org53ab7bb">更新前查询中的实体跟踪</a>
<ul>
<li><a href="#orgd4d3ec9">没有事务的情况下:</a></li>
<li><a href="#org5c83a57">有事务的情况下：</a></li>
</ul>
</li>
<li><a href="#org833a99c">使用Query进行update时的坑</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="outline-2" id="outline-container-org8363207">
<h2 id="org8363207">Transaction Annotation</h2>
<div class="outline-text-2" id="text-org8363207">
<div class="org-src-container">
<pre class="src src-java">
<span style="font-weight: bold; text-decoration: underline;">@Test</span>
<span style="font-weight: bold; text-decoration: underline;">@Transactional</span>
<span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">transactionMethod</span>() {
    repository.findAll();
    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">begin transaction</span>
    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">select * from ...</span>
    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">roll back</span>
}

<span style="font-weight: bold; text-decoration: underline;">@Test</span>
<span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">testTran</span>() {
    <span style="font-weight: bold;">this</span>.transactionMethod();
    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">no transaction</span>
}

</pre>
</div>
<p>
运行方法 transactionMethod 有事务，运行testTran没有事务。
</p>
<p>
TODO
</p>
<ul class="org-ul">
<li class="off">{% raw %} [ ] {% endraw %} 这是在单元测试中的结果，还需要在主代码中测试一遍</li>
</ul>
</div>
</div>
<div class="outline-2" id="outline-container-orgf5ce027">
<h2 id="orgf5ce027">Entity Tracking</h2>
<div class="outline-text-2" id="text-orgf5ce027">
</div>
<div class="outline-3" id="outline-container-org53ab7bb">
<h3 id="org53ab7bb">更新前查询中的实体跟踪</h3>
<div class="outline-text-3" id="text-org53ab7bb">
</div>
<div class="outline-4" id="outline-container-orgd4d3ec9">
<h4 id="orgd4d3ec9">没有事务的情况下:</h4>
<div class="outline-text-4" id="text-orgd4d3ec9">
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; text-decoration: underline;">List</span>&lt;<span style="font-weight: bold; text-decoration: underline;">T_User</span>&gt; <span style="font-weight: bold; font-style: italic;">items</span> = repository.findAll();
<span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">27个结果</span>
items.get(0).setCityName(String.valueOf(System.currentTimeMillis()));
items.get(1).setCityName(String.valueOf(System.currentTimeMillis()));
repository.saveAll(items);
repository.flush();
</pre>
</div>
<p>
生成的语句：
select * from t_user; // repository.findAll()
saveAll 没有生成语句，大概只是在内存中提交了
flush() 提交写入，生成了27个 {% raw %} select * from T_User where id=? {% endraw %} 和两个 update
</p>
<p>
推理：
没有事务的情况下，没有实体跟踪？
没有实体跟踪，所以saveAll无法检查到需要更新的实体。所以saveAll首先是将参数items都查询出来，然后比对有哪些变更的。最后生成update语句
</p>
</div>
</div>
<div class="outline-4" id="outline-container-org5c83a57">
<h4 id="org5c83a57">有事务的情况下：</h4>
<div class="outline-text-4" id="text-org5c83a57">
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; text-decoration: underline;">Void</span> <span style="font-weight: bold; font-style: italic;">v</span> = transactionTemplate.execute(status -&gt; {
        <span style="font-weight: bold; text-decoration: underline;">List</span>&lt;<span style="font-weight: bold; text-decoration: underline;">T_User</span>&gt; <span style="font-weight: bold; font-style: italic;">items</span> = repository.findAll();
        items.get(0).setCityName(String.valueOf(System.currentTimeMillis()));
        items.get(1).setCityName(String.valueOf(System.currentTimeMillis()));
        repository.saveAll(items);
        repository.flush();
        <span style="font-weight: bold;">return</span> <span style="font-weight: bold; text-decoration: underline;">null</span>;
    });
</pre>
</div>
<p>
生成的语句：
</p>
<ol class="org-ol">
<li>select * from t_user // repository.findAll()</li>
<li>两个update</li>
</ol>
<p>
有事务的情况下有实体跟踪，慎用没有事务时的JPA
</p>
</div>
</div>
</div>
<div class="outline-3" id="outline-container-org833a99c">
<h3 id="org833a99c">使用Query进行update时的坑</h3>
<div class="outline-text-3" id="text-org833a99c">
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; text-decoration: underline;">@Query</span>(<span style="font-style: italic;">"update t_user set status=:status where user_id=:userId"</span>)
<span style="font-weight: bold; text-decoration: underline;">@Modifying</span>
<span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">updateStatusByUserId</span>(<span style="font-weight: bold; text-decoration: underline;">String</span> <span style="font-weight: bold; font-style: italic;">userId</span>, <span style="font-weight: bold; text-decoration: underline;">int</span> <span style="font-weight: bold; font-style: italic;">status</span>);

<span style="font-weight: bold; text-decoration: underline;">@Transactional</span>
<span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">update</span>() {
    <span style="font-weight: bold; text-decoration: underline;">User</span> <span style="font-weight: bold; font-style: italic;">entity</span> = repository.findByUserId(userId);
    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">假设entity 的状态为1</span>
    repository.updateStatusByUserId(userId, 2);
    entity = repository.findByUserId(userId);
    <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">entity中显示状态仍然是1</span>
}

<span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">查询数据库，确实已经修改成2了</span>
</pre>
</div>
<p>
通过 Query，无论是JPQL还是原生SQL，内存中跟踪的实体都无法被改变，造成查询结果错误。
但repository.findByUserId这一行却发出select的SQL语句。
</p>
<p>
解决方法是在 repository.findByUserId 之前加上 entityManager.clear()
</p>
</div>
</div>
</div>
</div>
<div class="status" id="postamble">
<p class="date">Date: 2020-6-22</p>
<p class="author">Author: gdme1320</p>
<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p>
</div>
